#Board里面存储棋盘状态，包含转换坐标、获取当前状态（转换为NN输入）、执行下棋、判断输赢以及游戏是否结束
#Game控制游戏的进行，实时显示状态，包含绘制棋盘、对战模式以及自训练模式

from __future__ import print_function
import numpy as np
import math
import os
import random
from gomoku.MCTS_AlphaZero import MCTSPlayer
from gomoku.policy_value_net import PolicyValueNet
from gomoku.alpha_beta import ABplayer

class Game(object):
    #游戏进行
    def __init__(self):
        self.board = Board()
        self.best_policy = PolicyValueNet(15, 15, model_file='./gomoku/current_policy.model')
        self.mcts_player = MCTSPlayer(self.best_policy.policy_value_fun, cpuct=5, simu_time=300, selfplay=False)
        self.ab_player = ABplayer()

    #接口，输入一个指示对弈对象的值和棋盘列表
    #model：选择对战模式，0为mcts即强化学习训练的模型，1为alpha-beta剪枝算法
    #boardlist棋盘列表：三元组，如[[,,,],[,,,],......],前两位是位置（范围是[0,14]），末位是玩家（1是人，2是AI）
    def start_play(self,model,boardlist):
        self.board.init_board(boardlist)
        if model==0:
            move=self.mcts_player.get_action(self.board,temp=1e-3,return_prob=0)
        else:
            move=self.ab_player.get_action(self.board)
        return self.board.move_to_location(move)


class Board(object):
    #显示游戏棋盘
    def __init__(self):
        self.width = 15
        self.height = 15
        #棋盘状态存储为（位置move，棋色player）
        self.n_in_row = 5 #获胜条件
        self.players = [1, 2]  # player1 and player2

    def init_board(self, boardlist):
        #初始化棋盘
        #当前mcts或alphabeta执棋
        a=1
        self.current_player = self.players[a]
        # print('*****************************',a)
        #将输入的棋盘转化为可用格式
        self.states = {}
        self.availables = list(range(self.width * self.height))
        for i in range(len(boardlist)):
            point=self.location_to_move([boardlist[i][0],boardlist[i][1]])
            self.availables.remove(point)
            self.states[point]=boardlist[i][2]
        if len(boardlist)==0:
            self.last_move=-1
        else:
            self.last_move=self.location_to_move([boardlist[-1][0],boardlist[-1][1]])

    def move_to_location(self, move):
        #计算坐标转为实际坐标
        h = move // self.width #行
        w = move % self.width  #列
        return [h, w]

    def location_to_move(self, location):
        #实际坐标转为计算坐标
        #实际坐标不满足二维时，无效
        if len(location) != 2:
            return -1
        h = location[0]
        w = location[1]
        move = h * self.width + w
        #转换溢出时，无效
        if move not in range(self.width * self.height):
            return -1
        return move

    def current_state(self):
        #将当前状态变为神经网络的输入形式
        #4个二值平面：1当前玩家落棋，2对手落棋，3上一步落棋位置，4上一步落棋棋色
        square_state = np.zeros((4, self.width, self.height))
        if self.states:
            moves, players = np.array(list(zip(*self.states.items())))
            #当前玩家已下过的棋
            move_curr = moves[players == self.current_player]
            #对手玩家已下过的棋
            move_oppo = moves[players != self.current_player]
            #平面1：将当前玩家下过棋的位置设为1，其余为0
            square_state[0][move_curr // self.width,
                            move_curr % self.width] = 1.0
            #平面2：将对手玩家下过棋的位置设为1，其余为0
            square_state[1][move_oppo // self.width,
                            move_oppo % self.width] = 1.0
            #平面3：上一步棋的位置设为1，其余为0
            square_state[2][self.last_move // self.width,
                            self.last_move % self.width] = 1.0
        if len(self.states) % 2 == 0:
            #平面4：上一步棋为白棋所有位置为1，否则为0
            square_state[3][:, :] = 1.0
        return square_state

    def consider_avail(self):
        availtemp=[]
        if self.states:
            moves=list(set(range(self.width * self.height)) - set(self.availables))
            #moves,players=np.array(list(zip(*self.states.items())))
            for i in range(len(moves)):#只考虑棋子周边位置
                if (((moves[i]-1) not in moves) and ((moves[i]-1) in range(225))):#棋子左边位置
                    availtemp.append(moves[i]-1)
                if (((moves[i]+1) not in moves) and ((moves[i]+1) in range(225))):#棋子右边位置
                    availtemp.append(moves[i]+1)
                if (((moves[i]+14) not in moves) and ((moves[i]+14) in range(225))):#棋子左下位置
                    availtemp.append(moves[i]+14)
                if (((moves[i]+15) not in moves) and ((moves[i]+15) in range(225))):#棋子下边位置
                    availtemp.append(moves[i]+15)
                if (((moves[i]+16) not in moves) and ((moves[i]+16) in range(225))):#棋子右下位置
                    availtemp.append(moves[i]+16)
                if (((moves[i]-14) not in moves) and ((moves[i]-14) in range(225))):#棋子右上位置
                    availtemp.append(moves[i]-14)
                if (((moves[i]-15) not in moves) and ((moves[i]-15) in range(225))):#棋子上边位置
                    availtemp.append(moves[i]-15)
                if (((moves[i]-16) not in moves) and ((moves[i]-16) in range(225))):#棋子左上位置
                    availtemp.append(moves[i]-16)
        else:
            availtemp.append(112)
        availtemp=list(sorted(set(availtemp),key=availtemp.index))
        #print(availtemp)
        return availtemp


    def do_move(self, move):
        #执行move
        self.states[move] = self.current_player
        #新move移除可执行move
        self.availables.remove(move)
        #交换玩家
        self.current_player = (
            self.players[0] if self.current_player == self.players[1]
            else self.players[1])
        #更新上一步棋
        self.last_move = move

    def has_a_winner(self):
        #判断是否有胜者
        width = self.width
        height = self.height
        states = self.states
        n = self.n_in_row
        #将已下过的棋保存为列表
        moved = list(set(range(width * height)) - set(self.availables))
        #少于九步棋不应有胜利者
        if len(moved) < self.n_in_row + 4:
            return False, -1

        for m in moved:
            h = m // width
            w = m % width
            player = states[m]
            #横向连成一线
            if (w in range(width - n + 1) and
                    len(set(states.get(i) for i in range(m, m + n))) == 1):
                return True, player

            #纵向连成一线（以宽为步长）
            if (h in range(height - n + 1) and
                    len(set(states.get(i) for i in range(m, m + n * width, width))) == 1):
                return True, player

            #右下方连成一线（以宽+1为步长）
            if (w in range(width - n + 1) and h in range(height - n + 1) and
                    len(set(states.get(i) for i in range(m, m + n * (width + 1), width + 1))) == 1):
                return True, player

            #右上方连成一线（以宽-1为步长）
            if (w in range(n - 1, width) and h in range(height - n + 1) and
                    len(set(states.get(i) for i in range(m, m + n * (width - 1), width - 1))) == 1):
                return True, player

        return False, -1

    def game_end(self):
        #检查游戏是否结束
        win, winner = self.has_a_winner()
        if win:
            return True, winner
        #无人获胜但没有下棋位置，游戏结束
        elif not len(self.availables):
            return True, -1
        return False, -1

    def get_current_player(self):
        return self.current_player


# class Game(object):
#     #游戏进行
#
#     def __init__(self):
#         self.board = Board()
#         self.best_policy = PolicyValueNet(15, 15, model_file='./gomoku/current_policy.model')
#         self.mcts_player = MCTSPlayer(self.best_policy.policy_value_fun, cpuct=5, simu_time=100,selfplay=False)
#         self.ab_player = ABplayer()
#
#
#     def start_play(self,player,boardlist):
#         self.board.init_board(boardlist)
#         #选择对战模式，player(0)为mcts，player(1)为ab
#         if player==0:
#             move=self.mcts_player.get_action(self.board,temp=1e-3,return_prob=0)
#         else:
#             move=self.ab_player.get_action(self.board)
#         return self.board.move_to_location(move)